
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Collections;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Text;
using System.Xml;
using System.Security.Cryptography;

using Swaf;
using Swaf.BizObj;
using Swaf.BizObj.basic;
using Swaf.Container;
using Swaf.Crypto;
using Swaf.Xml;

using Syrinx;

namespace MediaCache
{
	/// <summary>
	/// Manages the image store directly within the file system. 
	/// </summary>
	/// <remarks>This class uses two directories, one as the "origionals" base location
	/// and the other as the "cache" base location.</remarks>
	public class FileSysImageCache : BaseImageCache
	{        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="req"></param>
        /// <param name="catalogName"></param>
		public FileSysImageCache(LineInfoXmlElement req, Swaf.Container.XmlCatalogContainerNode.NameInfo catalogName)
			:base(req, catalogName)
		{
			IApplication app = Application.currentApp;
			if(OrigionalImagesLocation.Length != 0)
				OrigionalImagesLocation = app.resMgr.resolve(OrigionalImagesLocation);
			else
				throw new MediaCacheException("OriginalImagesLocation must be specified in the XML for catalog entry '" + catalogName + "'");
            
			if(BaseCacheLocation.Length != 0)
				BaseCacheLocation = app.resMgr.resolve(BaseCacheLocation);
			else
				throw new MediaCacheException("BaseCacheLocation must be specified in the XML for catalog entry '" + catalogName + "'");
		}
        
        /// <summary>
        /// 
        /// </summary>
		public string OrigionalImagesLocation
		{
			get { return (string)m_cacheOptions.get("origionalImagesLocation"); }
			set { m_cacheOptions.put("origionalImagesLocation", value); }
		}

		/// <summary>
        /// 
        /// </summary>
		public string BaseCacheLocation
		{
			get{return (string)m_cacheOptions.get("baseCacheLocation");}
			set{m_cacheOptions.put("baseCacheLocation", value);}
		}

		#region BaseImageCache overrides
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="listOf"></param>
        /// <param name="fileFilters"></param>
        /// <returns></returns>
		public override IBizObjList getImages(ImageListType listOf, string fileFilters)
		{
            return getImages(null, listOf, fileFilters, DateTime.MinValue);
		}
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="path"></param>
        /// <param name="listOf"></param>
        /// <param name="fileFilters"></param>
        /// <returns></returns>
        public override IBizObjList getImages(string path, ImageListType listOf, string fileFilters, DateTime changedSince)
        {
            if (path == null)
                path = "";
			string dirPath = Cmn.pathCombine(OrigionalImagesLocation, path);
            //if (path != "")
            //    path += Path.DirectorySeparatorChar;
            IBizObjList li = Application.currentApp.bizObjMgr.createList(MediaBizObjType);
            DirectoryInfo di = new DirectoryInfo(dirPath);
            if (fileFilters == null)
                fileFilters = ((string)m_cacheOptions.get("fileFilter")).Trim();
            getImagesFromDirectory(li, di, fileFilters,path ,listOf, changedSince);
            return li;
        }

		protected virtual string MediaBizObjType { get { return "Image"; } }
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="baseLocation"></param>
        /// <returns></returns>
        public override IBizObjList getLocations(string baseLocation)
        {
            IBizObjList li = Application.currentApp.bizObjMgr.createList("MediaLocation");
			DirectoryInfo di = new DirectoryInfo(Cmn.pathCombine(OrigionalImagesLocation, baseLocation));
            getLocationTree(li, di);
            return li;
        }
        
        protected void getLocationTree(IBizObjList list, DirectoryInfo di)
        {
            foreach (DirectoryInfo childDi in di.GetDirectories())
				if ((childDi.Attributes & FileAttributes.Hidden) != FileAttributes.Hidden)
				{
					IBizObj loc = list.insertNew();
					loc.put("name", childDi.Name);
					//loc.put("fullName", childDi.FullName.Substring(OrigionalImagesLocation.Length +1));
					loc.put("fullName", childDi.FullName.Substring(OrigionalImagesLocation.Length ));
					getLocationTree(loc.get("childDirs") as IBizObjList, childDi);
				}
        }

        public override bool hasImageWithName(string imgName)
        {
            bool exists;
            string fullFileName = Cmn.pathCombine(OrigionalImagesLocation, imgName);
            getRealNameFromNameWithoutSizeInfo(new FileInfo(fullFileName), out exists);
            return exists;
        }        

        
		protected override bool isScaledImageOutdated(ref string origionalName, IBizObj sizing, IBizObj imgAdjInfo)
		{
			string origFileName = Path.Combine(OrigionalImagesLocation, origionalName);
			if (Cmn.IsEmpty(Path.GetExtension(origFileName)))
			{
				string ext = getOrigionalImageFileNameExt(origionalName);
				origFileName = new StringBuilder(200).Append(origFileName).Append(".").Append(ext).ToString();
				origionalName = new StringBuilder(200).Append(origionalName).Append(".").Append(ext).ToString();
			}
			FileInfo origFile = getRealNameFromNameWithoutSizeInfo(new FileInfo(origFileName));
			FileInfo scaledFile = new FileInfo(getCacheImageName(origionalName, sizing, imgAdjInfo));

			if (!origFile.Exists && Directory.Exists(origFile.DirectoryName))
			{
				string n = origFile.Name;
				int p = n.LastIndexOf('.');
				string[] files = Directory.GetFiles(origFile.DirectoryName, n.Substring(0, p + 1) + "*" + n.Substring(p));
				if (files != null && files.Length == 1)
					origFile = new FileInfo(files[0]);
				else
					return false;
			}
			return ((!scaledFile.Exists) || (origFile.LastWriteTimeUtc > scaledFile.LastWriteTimeUtc));
		}

        protected FileInfo getRealNameFromNameWithoutSizeInfo(FileInfo origFile)
        {
            bool exists;
            return getRealNameFromNameWithoutSizeInfo(origFile, out exists);
        }

		protected FileInfo getRealNameFromNameWithoutSizeInfo(FileInfo origFile, out bool exists)
		{
            exists = false;
            if (origFile.Exists)
                exists = true;
			else if (Directory.Exists(origFile.DirectoryName))
			{
				string n = origFile.Name;
				int p = n.LastIndexOf('.');
				string[] files = Directory.GetFiles(origFile.DirectoryName, n.Substring(0, p + 1) + "*" + n.Substring(p));
                if (files != null && files.Length == 1)
                {
                    exists = true;
                    origFile = new FileInfo(files[0]);
                }
			}
			return origFile;
		}
        
		protected string getCacheImageName(string origionalName, IBizObj scaleOptions, IBizObj imgAdjInfo)
		{
			System.Text.StringBuilder buff = new System.Text.StringBuilder(255);
			char dirSep = Path.DirectorySeparatorChar;
            buff.Append(BaseCacheLocation).Append(dirSep);
            string scaleName = "_default";
            if (!scaleOptions.get("name").Equals(""))
                scaleName = scaleOptions.get("name").ToString();
            buff.Append(scaleName).Append(dirSep);
            buff.Append(imgAdjInfo.get("name")).Append(dirSep);
			string dirName = Path.GetDirectoryName(origionalName);
			if(dirName.Length != 0)
				buff.Append(dirName).Append(dirSep);
			buff.Append(Path.GetFileNameWithoutExtension(origionalName));
			string imgType = imgAdjInfo.get("imageType").ToString();
			if(imgType == "*")
				buff.Append(origionalName.Substring(origionalName.LastIndexOf('.')));
			else
				buff.Append(".").Append(imgAdjInfo.get("imageType"));
			return buff.ToString();
		}
        
		protected override string getOrigionalImageFileNameExt(string imageFileName)
		{
			DirectoryInfo di = new DirectoryInfo(OrigionalImagesLocation);
			FileInfo[] files = di.GetFiles(imageFileName + ".*");
			if(files.Length > 0)
				return files[0].Extension.Substring(1);
			return null;
		}
        
		protected override IBizObj retrieveImage(string name, IBizObj scaleOptions, IBizObj imgAdjInfo)
		{
			IBizObj obj = Application.currentApp.bizObjMgr.create("ImageCacheImage");

			string fileName = calcName(name, scaleOptions, imgAdjInfo);
			FileInfo fi = getRealNameFromNameWithoutSizeInfo(new FileInfo(fileName));
			Stream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
			if(myCrypto != null)
				fs = myCrypto.createCryptoStream(fs);
			MediaStream m = new MediaStream(Path.GetExtension(fileName), fs, fileName);
			obj.put("id", new Swaf.BGuid.Guid());
			obj.put("name", Path.GetFileName(name));
			obj.put("location", Path.GetDirectoryName(name));
			obj.put("data", m);
			obj.put("creationDate", fi.CreationTimeUtc < fi.LastWriteTimeUtc?fi.CreationTimeUtc:fi.LastWriteTimeUtc);
			obj.put("type", m.MimeType);

			return obj;
		}
        
		protected override MediaStream retrieveImageStream(string name, IBizObj scaleOptions, IBizObj imgAdjInfo)
		{
			if (name == null || name == "") return null;

			string fileName = calcName(name, scaleOptions, imgAdjInfo);
			FileInfo origFileInfo = new FileInfo(fileName);
			FileInfo fileInfo = getRealNameFromNameWithoutSizeInfo(origFileInfo);
			if (!fileInfo.Exists)
				return null;

			Stream fs = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read);
			if(myCrypto != null)
				fs = myCrypto.createCryptoStream(fs);
			return new MediaStream(Path.GetExtension(fileName), fs, origFileInfo.Name);
		}
        
		protected override void saveImage(Bitmap image, string name, IBizObj scaleOptions, IBizObj imgAdjInfo, EncoderParameters eps)
		{
			string fileName = calcName(name, scaleOptions, imgAdjInfo);
			verifyDirectory(Path.GetDirectoryName(fileName));
			if(myCrypto == null)
				image.Save(fileName, getDesiredEncoder(Path.GetExtension(fileName).Substring(1)), eps);
			else
			{
				FileInfo fi = new FileInfo(fileName);
				FileStream fs = fi.OpenWrite();
                CryptoStream cs = myCrypto.createCryptoStream(fs) as CryptoStream;
				image.Save(cs,getDesiredEncoder(Path.GetExtension(fileName).Substring(1)), eps);
                cs.FlushFinalBlock();
				cs.Close();
				fs.Close();
			}
		}
		#endregion

        
		protected string calcName(string name, IBizObj scaleOptions, IBizObj imgAdjInfo)
		{
			string fileName = null;
			if(scaleOptions == null)
				fileName = Cmn.pathCombine(OrigionalImagesLocation, name);
			else
				fileName = getCacheImageName(name, scaleOptions, imgAdjInfo);
			return new FileInfo(fileName).FullName;
		}

        
		protected void verifyDirectory(string subName)
		{
			DirectoryInfo di = new DirectoryInfo(subName);
			if(!di.Exists)
				di.Create();
		}
        
		
		protected void getImagesFromDirectory(IBizObjList list, DirectoryInfo di,
			string fileFilters, string namePrefix, ImageListType listOf, DateTime changedSince)
		{
			
			string []filters = fileFilters.Split(';');
			foreach(string filter in filters)
				foreach(FileInfo fi in di.GetFiles(filter))
                   // if (fi.LastWriteTimeUtc > changedSince)
                   if (fi.CreationTimeUtc > changedSince)
                            processMediaFile(fi, list.insertNew(), namePrefix);

			if(listOf == ImageListType.deepFiles)
				foreach(DirectoryInfo childDi in di.GetDirectories())
					if ((childDi.Attributes & FileAttributes.Hidden) != FileAttributes.Hidden)
						getImagesFromDirectory(list, childDi, fileFilters,
							namePrefix + childDi.FullName.Substring(di.FullName.Length) + Path.DirectorySeparatorChar,
							listOf, changedSince);
		}

		protected virtual void processMediaFile(FileInfo fi, IBizObj imgObj, string namePrefix)
		{
			//check to see if file name has built it size info already
			int width = -1, height = -1;
			string n = fi.Name;
			int p1 = -1, p2 = -1, p3 = -1;
			if((p2 = n.LastIndexOf('.')) != -1 && (p1 = n.LastIndexOf('.', p2 - 1)) != -1)
			{
				if((p3 = n.IndexOf('x',p1)) != -1 && p3 < p2)
				{
					int.TryParse(n.Substring(p1+1, p3-p1-1), out width);
					int.TryParse(n.Substring(p3+1, p2-p3-1), out height);
					n = n.Substring(0, p1) + n.Substring(p2);
				}
			}

			string fullName = Cmn.pathCombine(namePrefix, n);
			if (fullName[0] == Path.DirectorySeparatorChar)
				fullName = fullName.Substring(1);
			if (ExtensionName != null && ExtensionName != "")
				fullName += "." + ExtensionName;
			imgObj.put("name", fullName.Replace('\\', '/'));
			imgObj.put("type", fi.Extension.Substring(1));
			imgObj.put("location", namePrefix);
			imgObj.put("cacheName", CacheName);
			imgObj.put("file.size", fi.Length);
			imgObj.put("file.dateCreated", fi.CreationTimeUtc);

			//Get Size from image if we dont have it from file name
			try
			{
				if (width == -1)
				{
					string newName = null;
					using (Bitmap i = new Bitmap(fi.FullName))
					{
						Size s = i.Size;
						imgObj.put("width", s.Width);
						imgObj.put("height", s.Height);
						p2 = fi.FullName.LastIndexOf('.');
						newName = fi.FullName.Substring(0, p2 + 1) + s.Width + "x" + s.Height + fi.FullName.Substring(p2);
					}
					if (newName != null)
						File.Move(fi.FullName, newName);
				}
				else
				{
					imgObj.put("width", width);
					imgObj.put("height", height);
				}

			}
			catch (Exception)
			{
				//Do nothing.
			}
		}
	}
}
